package br.bd2.protocolo2PL.managerObjects;

import java.util.ArrayList;
import java.util.List;

import br.bd2.protocolo2PL.EstruturaDeDados.Grafo;
import br.bd2.protocolo2PL.EstruturaDeDados.GrafoDeEspera;
import br.bd2.protocolo2PL.constantes.StatusLinhaBloqueio;
import br.bd2.protocolo2PL.constantes.TiposDeBloqueios;
import br.bd2.protocolo2PL.constantes.TiposDeObjetos;
import br.bd2.protocolo2PL.entity.LinhaDeBloqueio;
import br.bd2.protocolo2PL.entity.ObjetoDeBanco;
import br.bd2.protocolo2PL.entity.TabelaDeBloqueios;

public class Protocolo_2pl {

	private ManagerObjects objetos;
	private boolean isCompativel;
	private Grafo grafoDeEspera;
	private TabelaDeBloqueios tabelaCompatibilidade;
	private String schedule;
	private String scheduleTemporario;
	private String scheduleFinal;
	private String strOperacao;
	private String[] comandos;
	private List<LinhaDeBloqueio> tabelaDeBloqueio;
	private ObjetoDeBanco objeto;
	private ObjetoDeBanco objetoAux;
	private int delimitador;
	private int count;
	private int operacao;
	private String trid;
	private String obid;
	private LinhaDeBloqueio linha;
	private LinhaDeBloqueio linhaAux;;
	public static final String LEITURA = "rd";
	public static final String ESCRITA = "wr";
	public static final String UPDATE = "up";
	public static final String INTENCIONAL_LEITURA = "ir";
	public static final String INTENCIONAL_ESCRITA = "iw";
	public static final String INTENCIONAL_UPDATE = "iu";

	public Protocolo_2pl(String Schedule) {
		this.objetos = ManagerObjects.getInstance();
		this.tabelaCompatibilidade = TabelaDeBloqueios.getInstance();
		this.grafoDeEspera = GrafoDeEspera.getINSTANCE();
		this.schedule = Schedule;
		this.scheduleTemporario = "";
		this.scheduleFinal = "";
		this.tabelaDeBloqueio = new ArrayList<LinhaDeBloqueio>();
	}

	public String getEscalonamento() {
		comandos = schedule.split(";");
		for (String comando : comandos) {
			strOperacao = comando.substring(count, count + 2);
			delimitador = comando.indexOf(")");
			switch (strOperacao) {
			case LEITURA:
				operacao = TiposDeBloqueios.leitura;
				trid = comando.substring(count + 2, count + 3);
				obid = comando.substring(count + 4, delimitador);
				break;

			case UPDATE:
				operacao = TiposDeBloqueios.update;
				trid = comando.substring(count + 2, count + 3);
				obid = comando.substring(count + 4, delimitador);
				break;

			case ESCRITA:
				operacao = TiposDeBloqueios.escrita;
				trid = comando.substring(count + 2, count + 3);
				obid = comando.substring(count + 4, delimitador);
				break;

			case INTENCIONAL_LEITURA:
				operacao = TiposDeBloqueios.intencional_leitura;
				trid = comando.substring(count + 2, count + 3);
				obid = comando.substring(count + 4, delimitador);
				break;

			case INTENCIONAL_UPDATE:
				operacao = TiposDeBloqueios.intencional_update;
				trid = comando.substring(count + 2, count + 3);
				obid = comando.substring(count + 4, delimitador);
				break;

			case INTENCIONAL_ESCRITA:
				operacao = TiposDeBloqueios.intencional_escrita;
				trid = comando.substring(count + 2, count + 3);
				obid = comando.substring(count + 4, delimitador);
				break;

			default:
				break;
			}

			linha = new LinhaDeBloqueio(trid, obid, operacao,
					StatusLinhaBloqueio.AGUARDANDO);

			if (tabelaDeBloqueio.isEmpty()) {
				linha.setStatus(StatusLinhaBloqueio.CONCEDIDO);
				objeto = objetos.getObjeto(linha.getObid());

			}
			this.adicionarLinhaDeBloqueio(linha);

			for (LinhaDeBloqueio linha : tabelaDeBloqueio) {
				System.out.printf(
						"Trid: %s obid: %s operacao: %d status: %s \n",
						linha.getTrid(), linha.getObid(), linha.getOperacao(),
						linha.getStatus());

			}
			System.out.println("\n\n\n");

		}

		return schedule;

	}

	/**
	 * Procura o pai de um objeto
	 * 
	 * @param obid
	 * @return
	 */
	private ObjetoDeBanco getPai(String obid) {
		objeto = objetos.getObjeto(obid);
		for (ObjetoDeBanco obj : objetos.getObjetos()) {
			if (obj.getTipo().equals(TiposDeObjetos.page)
					|| obj.getTipo().equals(TiposDeObjetos.tabela)) {
				for (ObjetoDeBanco obj2 : obj.getObjetos()) {
					if (obj2.getObid().equalsIgnoreCase(obid)) {
						objeto = obj;
						return objeto;
					}
				}
			}
		}
		return objeto;
	}

	/**
	 * Adiciona os intencionais
	 * 
	 * @param linha
	 */
	private void adicionarLinhaDeBloqueio(LinhaDeBloqueio linha) {
		boolean transacaoUnica = true;
		trid = linha.getTrid();
		for (LinhaDeBloqueio linhaTab : tabelaDeBloqueio) {
			if (!linhaTab.getTrid().equalsIgnoreCase(linha.getTrid())) {
				transacaoUnica = false;
				break;
			}
		}

		if (transacaoUnica) {
			switch (objeto.getTipo()) {
			case row:
				objeto = this.getPai(objeto.getObid());
				switch (linha.getOperacao()) {
				case TiposDeBloqueios.leitura:
					linhaAux = new LinhaDeBloqueio(trid, objeto.getObid(),
							TiposDeBloqueios.intencional_leitura,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.escrita:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_escrita,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.update:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_update,
							StatusLinhaBloqueio.CONCEDIDO);
					break;
				}
				grafoDeEspera.adicionarVertice(linha.getTrid());
				tabelaDeBloqueio.add(linha);
				tabelaDeBloqueio.add(linhaAux);
				adicionarLinhaDeBloqueio(linhaAux);
				break;

			case page:
				objeto = this.getPai(objeto.getObid());
				switch (linha.getOperacao()) {
				case TiposDeBloqueios.leitura:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_leitura,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.escrita:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_escrita,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.update:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_update,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.intencional_leitura:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_leitura,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.intencional_escrita:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_escrita,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.intencional_update:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_update,
							StatusLinhaBloqueio.CONCEDIDO);
					break;
				}
				tabelaDeBloqueio.add(linha);
				break;

			case tabela:
				objeto = this.getPai(objeto.getObid());
				switch (linha.getOperacao()) {
				case TiposDeBloqueios.leitura:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(), TiposDeBloqueios.leitura,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.escrita:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(), TiposDeBloqueios.escrita,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.update:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(), TiposDeBloqueios.update,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.intencional_leitura:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_leitura,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.intencional_escrita:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_escrita,
							StatusLinhaBloqueio.CONCEDIDO);
					break;

				case TiposDeBloqueios.intencional_update:
					linha = new LinhaDeBloqueio(linha.getTrid(),
							objeto.getObid(),
							TiposDeBloqueios.intencional_update,
							StatusLinhaBloqueio.CONCEDIDO);
					break;
				}
				tabelaDeBloqueio.add(linha);
				break;

			}
		} else if (!transacaoUnica) {
			objeto = objetos.getObjeto(linha.getObid());
			grafoDeEspera.adicionarVertice(linha.getTrid());
			switch (objeto.getTipo()) {

			case row:

				break;

			case page:
				break;

			case tabela:

				for (LinhaDeBloqueio l : tabelaDeBloqueio) {
					objetoAux = objetos.getObjeto(l.getObid());
					if (objetoAux.getTipo().equals(TiposDeObjetos.tabela) && l.getStatus().equals(StatusLinhaBloqueio.CONCEDIDO)) {
						if (!l.getTrid().equalsIgnoreCase(linha.getTrid())) {
							isCompativel = tabelaCompatibilidade.isCompatible(l.getOperacao(), linha.getOperacao());
							if (isCompativel) {
								linha.setStatus(StatusLinhaBloqueio.CONCEDIDO);
							} else {
								grafoDeEspera.adicionarAresta(linha.getTrid(),l.getTrid());
								if (grafoDeEspera.cycleDetect()) {
									System.out.println("Deadlock detectado entre as transacoes " + linha.getTrid() + " e " + l.getTrid()+ "!");
									for (LinhaDeBloqueio l2 : tabelaDeBloqueio) {
										if (l2.getStatus().equals(StatusLinhaBloqueio.CONCEDIDO)) {
											if(l2.getTrid().equals(l.getTrid())){
												System.out.println("transacao " + linha.getTrid() + " escolhida pra ser abortada por ser mais recente");
												//grafoDeEspera.removerVertice(l.getTrid());
												break;
											}
											} else if(l2.getTrid().equalsIgnoreCase(linha.getTrid())){
												System.out.println("transacao " + l.getTrid() + " escolhida pra ser abortada por ser mais recente");
												//grafoDeEspera.removerVertice(linha.getTrid());
												break;
											}
										
											
									}
								}
							}
						}

					}
				}
				tabelaDeBloqueio.add(linha);
				break;

			}
		}

	}

	public static void main(String[] args) {
		String schedule1 = "rd1(t1);wr2(t2);rd3(tab1);co1;co2;co3";
		String schedule2 = "rd2(tab1);rd1(tab1);wr1(tab1);wr2(tab1)";
		Protocolo_2pl protocolo = new Protocolo_2pl(schedule2);
		protocolo.getEscalonamento();
	}

}
